﻿namespace TestApp.Tests
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reactive;
    using System.Reactive.Subjects;
    using Contracts;
    using DataService;
    using Microsoft.Reactive.Testing;
    using Moq;
    using NUnit.Framework;
    using Shouldly;

    [TestFixture]
    internal class DataSenderFixture
    {
        private readonly HashSet<EntityDto> _entitiesToSend = new HashSet<EntityDto>();
        private readonly List<List<EntityDto>> _entitiesToReceive = new List<List<EntityDto>>();
        private Mock<IDataAggregator> _dataAggregator;
        private Mock<IDataServiceCallback> _callback;
        private ISubject<Unit> _hasNewEntitySubject;
        private long _dataVersion;
        private IEnumerable<EntityDto> _entities;
        private DataEntityDto _entity0;
        private DataEntityDto _entity1;
        private DataEntityDto _entity2;
        private TestScheduler _scheduler;
        private Mock<IDisposable> _anchors;

        [SetUp]
        public void SetUp()
        {
            _anchors = new Mock<IDisposable>();
            _scheduler = new TestScheduler();
            _entitiesToSend.Clear();
            _entitiesToReceive.Clear();
            _callback = new Mock<IDataServiceCallback>();
            _dataAggregator = new Mock<IDataAggregator>();

            _hasNewEntitySubject = new BehaviorSubject<Unit>(Unit.Default);
            _dataAggregator.Setup(i => i.CreateHasNewEntitySource()).Returns(_hasNewEntitySubject);
            _dataAggregator.Setup(i => i.TryGetEntities(It.IsAny<long>(), out _dataVersion, out _entities)).Returns(false);
            _callback.Setup(i => i.ProcessEntities(It.IsAny<IEnumerable<EntityDto>>())).Callback<IEnumerable<EntityDto>>(i => _entitiesToReceive.Add(i.ToList()));

            _entity0 = new DataEntityDto(0);
            _entity0.Parameters[2] = 1d;

            _entity1 = new DataEntityDto(1);
            _entity1.Parameters[2] = -1d;

            _entity2 = new DataEntityDto(2);
            _entity2.Parameters[2] = .3d;
        }

        [Test]
        public void ShouldSendEntitiesWhenInitialize()
        {
            // Given
            var target = CreateInstance();

            // When
            target.Initialize(_anchors.Object, _callback.Object);
            SendEntity(_entity0);
            _scheduler.AdvanceBy(1);

            // Then
            _entitiesToReceive.Count.ShouldBe(1);
            _entitiesToReceive[0].Count.ShouldBe(1);
            _entitiesToReceive[0][0].ShouldBe(_entity0);
        }

        [Test]
        public void ShouldNotSendEntitiesWhenInitializeAdNoData()
        {
            // Given
            var target = CreateInstance();

            // When
            target.Initialize(_anchors.Object, _callback.Object);
            _scheduler.AdvanceBy(1);

            // Then
            _entitiesToReceive.Count.ShouldBe(0);
        }

        private IDataSender CreateInstance()
        {
            return new DataSender(_scheduler, _dataAggregator.Object);
        }

        private void SendEntity(EntityDto entity)
        {
            _entitiesToSend.Add(entity);
            _dataVersion++;
            _entities = _entitiesToSend.ToArray();
            _dataAggregator.Setup(i => i.TryGetEntities(It.IsAny<long>(), out _dataVersion, out _entities)).Returns(true);
            _hasNewEntitySubject.OnNext(Unit.Default);
        }
    }
}
